package org.jaronsource.framework.plugins.staticpage;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;

public class StaticPageFilter implements Filter {

	protected Log logger = LogFactory.getLog( StaticPageFilter.class );

	protected StaticPageContext staticPageContext = StaticPageContext.getInstance();

	protected Map<String, ReentrantReadWriteLock> locks = new HashMap<String, ReentrantReadWriteLock>();

	protected ReentrantReadWriteLock locksLock = new ReentrantReadWriteLock();

	protected ReentrantReadWriteLock getLock( String key ) {

		try {
			locksLock.writeLock().lock();
			ReentrantReadWriteLock lock = locks.get( key );
			if ( lock == null ) lock = new ReentrantReadWriteLock();
			locks.put( key, lock );
			return lock;
		}
		finally {
			locksLock.writeLock().unlock();
		}
	}

	@Override
	public void init( FilterConfig filterConfig ) throws ServletException {
		Assert.notNull( filterConfig, "FilterConfig must not be null" );
		if ( logger.isDebugEnabled() ) {
			logger.debug( "Initializing filter '" + filterConfig.getFilterName() + "'" );
		}

		String encoding = filterConfig.getInitParameter( "encoding" );
		if ( encoding != null ) staticPageContext.setEncoding( encoding );

		String staticPageRoot = filterConfig.getInitParameter( "staticPageRoot" );
		if ( staticPageRoot != null ) staticPageContext.setEncoding( staticPageRoot );

		String requestExt = filterConfig.getInitParameter( "requestExt" );
		if ( requestExt != null ) staticPageContext.setRequestExt( requestExt );

		String staticPageExt = filterConfig.getInitParameter( "staticPageExt" );
		if ( staticPageExt != null ) staticPageContext.setEncoding( staticPageExt );

		if ( logger.isDebugEnabled() ) {
			logger.debug( "Filter '" + filterConfig.getFilterName() + "' configured successfully" );
		}
	}

	@Override
	public void doFilter(	ServletRequest request,
							ServletResponse response,
							FilterChain chain )	throws IOException,
												ServletException {

		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;

		String requestURI = httpServletRequest.getRequestURI();
		String contextPath = httpServletRequest.getContextPath();
		requestURI = requestURI.substring( contextPath.length() );

		String requestPath = StaticPageUtils.convertFromHtmlFileName( requestURI );

		String staticPageRootRealPath = httpServletRequest.getSession().getServletContext().getRealPath( staticPageContext.getStaticPageRoot() );
		String staticPageName = StaticPageUtils.convertToHtmlFileName( requestURI );
		String staticPageFilePath = staticPageRootRealPath + File.separator + staticPageName;
		logger.debug( "cacheFileName = " + staticPageFilePath );

		File cacheFile = new File( staticPageFilePath );
		boolean load = true;
		if ( cacheFile.exists() ) load = false;

		if ( load ) {
			final ByteArrayOutputStream os = new ByteArrayOutputStream();
			final ServletOutputStream stream = new ServletOutputStream() {

				@Override
				public void write( byte[] data, int offset, int length ) throws IOException {
					os.write( data, offset, length );
				}

				@Override
				public void write( int b ) throws IOException {
					os.write( b );
				}

			};

			final PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, staticPageContext.getEncoding() ) );
			HttpServletResponse rep = new HttpServletResponseWrapper( httpServletResponse ) {

				public ServletOutputStream getOutputStream() {
					return stream;
				}

				public PrintWriter getWriter() {
					return pw;
				}
			};

			logger.debug( "StaticPageFilter RequestDispatcher = " + requestPath );

			RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher( requestPath );
			dispatcher.include( httpServletRequest, rep );
			// pw.flush();

			FileOutputStream fos = null;
			try {
				if ( os.size() == 0 ) {
					httpServletResponse.sendError( HttpServletResponse.SC_NOT_FOUND, "" );
					return;
				}
				else {

					ReentrantReadWriteLock lock = getLock( staticPageFilePath );

					try {
						lock.writeLock().lock();
						File directory = cacheFile.getParentFile();
						if ( !directory.exists() ) directory.mkdirs();

						fos = new FileOutputStream( staticPageFilePath );
						os.writeTo( fos );
					}
					finally {
						lock.writeLock().unlock();
					}
				}
			}
			finally {
				if ( fos != null ) {
					fos.close();
				}
			}
		}

		RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher( staticPageContext.getStaticPageRoot() + staticPageName );
		dispatcher.forward( httpServletRequest, httpServletResponse );
	}

	@Override
	public void destroy() {}

}
